package com.clementheliou.cheliou.dal.model.photo;

import java.io.Serializable;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.persistence.Basic;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import javax.persistence.Transient;
import javax.persistence.UniqueConstraint;
import javax.validation.constraints.Digits;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

import org.apache.commons.lang3.StringUtils;
import org.hibernate.annotations.Type;
import org.joda.time.LocalDateTime;

import com.clementheliou.cheliou.dal.model.base.BaseEntity;
import com.clementheliou.cheliou.dal.model.camera.model.CameraModel;
import com.clementheliou.cheliou.dal.model.city.City;
import com.clementheliou.cheliou.dal.model.photo.enumeration.Direction;
import com.clementheliou.cheliou.dal.model.photo.enumeration.FlashMode;
import com.clementheliou.cheliou.dal.model.photo.enumeration.WhiteBalance;
import com.clementheliou.cheliou.dal.model.photo.util.PhotoUtils;

/**
 * Entity dedicated to the photo data.
 * 
 * @author Clément HELIOU (clement.heliou@gmail.com)
 * @see {@link BaseEntity};
 * @see {@link Entity};
 * @see {@link Serializable};
 * @see {@link Table};
 * @see {@link UniqueConstraint}.
 * @since 1.0
 */
@Entity
@Table(name = "photo", uniqueConstraints = @UniqueConstraint(columnNames = "code"))
public class Photo extends BaseEntity implements Serializable {

	/**
	 * Separator to use during flash modes conversions.
	 * 
	 * @author Clément HELIOU (clement.heliou@gmail.com)
	 * @since 1.0
	 */
	private static final char FLASH_MODES_SEPARATOR = ',';

	/**
	 * UID for serialization.
	 * 
	 * @author Clément HELIOU (clement.heliou@gmail.com)
	 * @since 1.0
	 */
	private static final long serialVersionUID = 277036452407154796L;

	/**
	 * The photo's code.
	 * 
	 * @author Clément HELIOU (clement.heliou@gmail.com)
	 * @since 1.0
	 */
	@Basic(optional = false)
	@Column(length = 23, nullable = false)
	@NotNull
	@Size(max = 23)
	private String code;

	/**
	 * The photo's shooting date.
	 * 
	 * @author Clément HELIOU (clement.heliou@gmail.com)
	 * @since 1.0
	 */
	private LocalDateTime shootingDate;

	/**
	 * The photo's direction.
	 * 
	 * @author Clément HELIOU (clement.heliou@gmail.com)
	 * @since 1.0
	 */
	private Direction direction;

	/**
	 * The photo's title key (that will be used for translation).
	 */
	@Basic(optional = false)
	@Column(length = 255, nullable = false)
	@NotNull
	@Size(max = 255)
	private String titleKey;

	/**
	 * The photo's latitude.
	 * 
	 * @author Clément HELIOU (clement.heliou@gmail.com)
	 * @since 1.0
	 */
	@Basic(optional = false)
	@Column(nullable = false)
	@Digits(integer = 2, fraction = 7)
	@NotNull
	private BigDecimal latitude;

	/**
	 * The photo's longitude.
	 * 
	 * @author Clément HELIOU (clement.heliou@gmail.com)
	 * @since 1.0
	 */
	@Basic(optional = false)
	@Column(nullable = false)
	@Digits(integer = 3, fraction = 7)
	@NotNull
	private BigDecimal longitude;

	/**
	 * The photo's addition date.
	 * 
	 * @author Clément HELIOU (clement.heliou@gmail.com)
	 * @since 1.0
	 */

	private LocalDateTime additionDate;

	/**
	 * The camera model that has been used to take this photo.
	 * 
	 * @author Clément HELIOU (clement.heliou@gmail.com)
	 * @since 1.0
	 */
	private CameraModel cameraModel;

	/**
	 * The photo's focal.
	 * 
	 * @author Clément HELIOU (clement.heliou@gmail.com)
	 * @since 1.0
	 */
	@Basic(optional = false)
	@Column(nullable = false)
	@Digits(integer = 2, fraction = 1)
	@NotNull
	private BigDecimal focal;

	/**
	 * The photo's focal distance (in mm).
	 * 
	 * @author Clément HELIOU (clement.heliou@gmail.com)
	 * @since 1.0
	 */
	@Basic(optional = false)
	@Column(nullable = false)
	@NotNull
	private Integer focalDistance;

	/**
	 * The photo's focal distance (35mm equivalence).
	 * 
	 * @author Clément HELIOU (clement.heliou@gmail.com)
	 * @since 1.0
	 */
	@Basic(optional = false)
	@Column(nullable = false)
	@NotNull
	private Integer focalDistance35mm;

	/**
	 * Has the photo's brightness been modified?
	 * 
	 * @author Clément HELIOU (clement.heliou@gmail.com)
	 * @since 1.0
	 */
	private Boolean brightnessModified;

	/**
	 * Has the photo's contrast been modified?
	 * 
	 * @author Clément HELIOU (clement.heliou@gmail.com)
	 * @since 1.0
	 */
	private Boolean contrastModified;

	/**
	 * Has the photo's layers been modified?
	 * 
	 * @author Clément HELIOU (clement.heliou@gmail.com)
	 * @since 1.0
	 */
	private Boolean layersModified;

	/**
	 * Has the photo's color been changed to B&W?
	 * 
	 * @author Clément HELIOU (clement.heliou@gmail.com)
	 * @since 1.0
	 */
	@Basic(optional = true)
	@Column(nullable = true)
	private Boolean blackAndWhite;

	/**
	 * Has the photo's been straightened?
	 * 
	 * @author Clément HELIOU (clement.heliou@gmail.com)
	 * @since 1.0
	 */
	@Basic(optional = true)
	@Column(nullable = true)
	private Boolean straighteningUp;

	/**
	 * Is there a model on the photo?
	 * 
	 * @author Clément HELIOU (clement.heliou@gmail.com)
	 * @since 1.0
	 */
	@Basic(optional = false)
	@Column(nullable = false)
	@NotNull
	private Boolean model;

	/**
	 * If there is a model on the photo, this field contains its name.
	 * 
	 * @author Clément HELIOU (clement.heliou@gmail.com)
	 * @since 1.0
	 */
	@Basic(optional = true)
	@Column(length = 52, nullable = true)
	@Size(max = 52)
	private String modelName;

	/**
	 * The photo's exposure time (in seconds).
	 * 
	 * @author Clément HELIOU (clement.heliou@gmail.com)
	 * @since 1.0
	 */
	@Basic(optional = false)
	@Column(nullable = false)
	@Min(1)
	@Max(8000)
	@NotNull
	private Integer exposureTime;

	/**
	 * The photo's ISO speed.
	 * 
	 * @author Clément HELIOU (clement.heliou@gmail.com)
	 * @since 1.0
	 */
	@Basic(optional = false)
	@Column(nullable = false)
	@Min(200)
	@Max(64000)
	@NotNull
	private Integer iso;

	/**
	 * The photo's white balance mode.
	 * 
	 * @author Clément HELIOU (clement.heliou@gmail.com)
	 * @since 1.0
	 */
	private WhiteBalance whiteBalanceMode;

	/**
	 * The flash mode used to take the photo.
	 * 
	 * @author Clément HELIOU (clement.heliou@gmail.com)
	 * @since 1.0
	 */
	private String flashMode;

	/**
	 * The photo's relative aperture.
	 * 
	 * @author Clément HELIOU (clement.heliou@gmail.com)
	 * @since 1.0
	 */
	@Basic(optional = false)
	@Column(nullable = false)
	@Digits(integer = 3, fraction = 1)
	@NotNull
	private BigDecimal relativeAperture;

	/**
	 * The city where the photo has been taken.
	 * 
	 * @author Clément HELIOU (clement.heliou@gmail.com)
	 * @since 1.0
	 */
	private City city;

	/**
	 * Is the value of {@link #exposureTime} greater or equals to 1?
	 * 
	 * @author Clément HELIOU (clement.heliou@gmail.com)
	 * @since 1.0
	 */
	@Basic(optional = false)
	@Column(nullable = false)
	private Boolean slowExposureTime;

	/**
	 * Gets the technical data sheet version of this photo.
	 * 
	 * @author Clément HELIOU (clement.heliou@gmail.com)
	 * @return the resulting path, ready to be used.
	 * @see {@link Transient}.
	 * @since 1.0
	 */
	@Transient
	public String getTechnicalDataSheetPath() {
		return PhotoUtils.getTechnicalDataSheetPath(this.code);
	}

	/**
	 * Gets the large version of this photo.
	 * 
	 * @author Clément HELIOU (clement.heliou@gmail.com)
	 * @return the resulting path, ready to be used.
	 * @see {@link Transient}.
	 * @since 1.0
	 */
	@Transient
	public String getLargePath() {
		return PhotoUtils.getLargePath(this.code);
	}

	/**
	 * Gets the medium version of this photo.
	 * 
	 * @author Clément HELIOU (clement.heliou@gmail.com)
	 * @return the resulting path, ready to be used.
	 * @see {@link Transient}.
	 * @since 1.0
	 */
	@Transient
	public String getMediumPath() {
		return PhotoUtils.getMediumPath(this.code);
	}

	/**
	 * Gets the small version of this photo.
	 * 
	 * @author Clément HELIOU (clement.heliou@gmail.com)
	 * @return the resulting path, ready to be used.
	 * @see {@link Transient}.
	 * @since 1.0
	 */
	@Transient
	public String getSmallPath() {
		return PhotoUtils.getSmallPath(this.code);
	}

	/**
	 * Gets the gallery version of this photo.
	 * 
	 * @author Clément HELIOU (clement.heliou@gmail.com)
	 * @return the resulting path, ready to be used.
	 * @see {@link Transient}.
	 * @since 1.0
	 */
	@Transient
	public String getGalleryPath() {
		return PhotoUtils.getGalleryPath(this.code);
	}

	/**
	 * The author's notes about this photo.
	 * 
	 * @author Clément HELIOU (clement.heliou@gmail.com)
	 * @since 1.0
	 */
	private Set<AuthorsNote> authorsNotes = new HashSet<AuthorsNote>();

	/*
	 * Getters & setters.
	 */

	@GeneratedValue(generator = "seq_photo")
	@Id
	@SequenceGenerator(name = "seq_photo", sequenceName = "seq_photo")
	public Integer getId() {
		return id;
	}

	public String getCode() {
		return code;
	}

	public void setCode(String code) {
		this.code = code;
	}

	@Basic(optional = false)
	@Column(nullable = false)
	@NotNull
	@Type(type = "org.jadira.usertype.dateandtime.joda.PersistentLocalDateTime")
	public LocalDateTime getShootingDate() {
		return shootingDate;
	}

	@Transient
	public Date getShootingJdkDate() {
		return shootingDate.toDate();
	}

	public void setShootingDate(LocalDateTime shootingDate) {
		this.shootingDate = shootingDate;
	}

	@Basic(optional = false)
	@Column(nullable = false, length = 9)
	@Enumerated(EnumType.STRING)
	@NotNull
	public Direction getDirection() {
		return direction;
	}

	public void setDirection(Direction direction) {
		this.direction = direction;
	}

	public String getTitleKey() {
		return titleKey;
	}

	public void setTitleKey(String titleKey) {
		this.titleKey = titleKey;
	}

	public BigDecimal getLatitude() {
		return latitude;
	}

	public void setLatitude(BigDecimal latitude) {
		this.latitude = latitude;
	}

	public BigDecimal getLongitude() {
		return longitude;
	}

	public void setLongitude(BigDecimal longitude) {
		this.longitude = longitude;
	}

	@Basic(optional = false)
	@Column(nullable = false)
	@NotNull
	@Type(type = "org.jadira.usertype.dateandtime.joda.PersistentLocalDateTime")
	public LocalDateTime getAdditionDate() {
		return additionDate;
	}

	@Transient
	public Date getAdditionJdkDate() {
		return additionDate.toDate();
	}

	public void setAdditionDate(LocalDateTime additionDate) {
		this.additionDate = additionDate;
	}

	@JoinColumn(name = "cameramodelid", nullable = false)
	@ManyToOne
	@NotNull
	public CameraModel getCameraModel() {
		return cameraModel;
	}

	public void setCameraModel(CameraModel cameraModel) {
		this.cameraModel = cameraModel;
	}

	public Integer getFocalDistance() {
		return focalDistance;
	}

	public void setFocalDistance(Integer focalDistance) {
		this.focalDistance = focalDistance;
	}

	public Integer getFocalDistance35mm() {
		return focalDistance35mm;
	}

	public void setFocalDistance35mm(Integer focalDistance35mm) {
		this.focalDistance35mm = focalDistance35mm;
	}

	@Basic(optional = true)
	@Column(name = "brightness", nullable = true)
	public Boolean getBrightnessModified() {
		return brightnessModified;
	}

	public void setBrightnessModified(Boolean brightnessModified) {
		this.brightnessModified = brightnessModified;
	}

	@Basic(optional = true)
	@Column(name = "contrast", nullable = true)
	public Boolean getContrastModified() {
		return contrastModified;
	}

	public void setContrastModified(Boolean contrastModified) {
		this.contrastModified = contrastModified;
	}

	@Basic(optional = true)
	@Column(name = "layers", nullable = true)
	public Boolean getLayersModified() {
		return layersModified;
	}

	public void setLayersModified(Boolean layersModified) {
		this.layersModified = layersModified;
	}

	public Boolean getBlackAndWhite() {
		return blackAndWhite;
	}

	public void setBlackAndWhite(Boolean blackAndWhite) {
		this.blackAndWhite = blackAndWhite;
	}

	public Boolean getStraighteningUp() {
		return straighteningUp;
	}

	public void setStraighteningUp(Boolean straighteningUp) {
		this.straighteningUp = straighteningUp;
	}

	public Boolean getModel() {
		return model;
	}

	public void setModel(Boolean model) {
		this.model = model;
	}

	public String getModelName() {
		return modelName;
	}

	public void setModelName(String modelName) {
		this.modelName = modelName;
	}

	@Basic(optional = false)
	@Column(length = 6, name = "whitebalance", nullable = false)
	@Enumerated(EnumType.STRING)
	@NotNull
	public WhiteBalance getWhiteBalanceMode() {
		return whiteBalanceMode;
	}

	public void setWhiteBalanceMode(WhiteBalance whiteBalanceMode) {
		this.whiteBalanceMode = whiteBalanceMode;
	}

	public BigDecimal getFocal() {
		return focal;
	}

	public void setFocal(BigDecimal focal) {
		this.focal = focal;
	}

	public Integer getExposureTime() {
		return exposureTime;
	}

	public void setExposureTime(Integer exposureTime) {
		this.exposureTime = exposureTime;
	}

	public Integer getIso() {
		return iso;
	}

	public void setIso(Integer iso) {
		this.iso = iso;
	}

	public BigDecimal getRelativeAperture() {
		return relativeAperture;
	}

	public void setRelativeAperture(BigDecimal relativeAperture) {
		this.relativeAperture = relativeAperture;
	}

	@JoinColumn(name = "cityid", nullable = false)
	@ManyToOne
	@NotNull
	public City getCity() {
		return city;
	}

	public void setCity(City city) {
		this.city = city;
	}

	@OneToMany(fetch = FetchType.EAGER, mappedBy = "annotatedPhoto")
	public Set<AuthorsNote> getAuthorsNotes() {
		return authorsNotes;
	}

	public void setAuthorsNotes(Set<AuthorsNote> authorsNotes) {
		this.authorsNotes = authorsNotes;
	}

	public Boolean getSlowExposureTime() {
		return slowExposureTime;
	}

	public void setSlowExposureTime(Boolean slowExposureTime) {
		this.slowExposureTime = slowExposureTime;
	}

	@Basic(optional = false)
	@Column(length = 255, nullable = false)
	@NotNull
	@Size(max = 255)
	public String getFlashMode() {
		return flashMode;
	}

	/**
	 * Gets the flash modes as a list of {@link FlashMode}.
	 * 
	 * @author Clément HELIOU (clement.heliou@gmail.com)
	 * @return the resulting flash modes list.
	 * @since 1.0
	 */
	@Transient
	public List<FlashMode> getFlashModes() {
		final List<FlashMode> flashModes = new ArrayList<FlashMode>();
		final String[] flashModesNames = StringUtils.split(this.flashMode, FLASH_MODES_SEPARATOR);

		for (String flashModeName : flashModesNames) {
			flashModes.add(FlashMode.valueOf(flashModeName));
		}

		return flashModes;
	}

	public void setFlashMode(String flashMode) {
		this.flashMode = flashMode;
	}

	/**
	 * Sets the flash mode with a list of {@link FlashMode}.
	 * 
	 * @author Clément HELIOU (clement.heliou@gmail.com)
	 * @param flashModes the flash modes list to be set.
	 * @since 1.0
	 */
	public void setFlashModes(final List<FlashMode> flashModes) {
		final String[] flashModesNames = new String[flashModes.size()];

		for (int i = 0; i < flashModes.size(); i++) {
			flashModesNames[i] = flashModes.get(i).name();
		}

		this.flashMode = StringUtils.join(flashModesNames, FLASH_MODES_SEPARATOR);
	}

}
